#pragma once
#include <iostream>
#include <vector>
#include <string>
#include <thread>
#include <queue>
#include <numeric>

class TimeCounter { public:std::chrono::steady_clock::time_point start; TimeCounter() :start(std::chrono::steady_clock::now()) { std::cout << "Start Test overhead." << std::endl; }~TimeCounter() { std::chrono::duration<double> dur = std::chrono::steady_clock::now() - start; std::cout <<"Execute spend " << dur.count() << " seconds." << std::endl; } };

struct Subject
{
    uint32_t id = 0;
    std::vector<double> startPrices;
    std::vector<double> closedPrices;
    std::vector<double> lowPrices;
    std::vector<double> highPrices;
    std::vector<double> meanPrices;

    std::vector<double> tradedNum;
    std::vector<double> tradedQuant;
    std::vector<double> diffRate;
    std::vector<double> yieldRate;
    std::vector<double> yieldQuant;
    std::vector<double> changedRate;

    std::vector<double> MA5;
    std::vector<double> MA10;
    std::vector<double> MA20;
    std::vector<double> MA60;
    std::vector<double> MA120;
    std::vector<double> MA250;

    std::vector<double> MACD;

    std::vector<double> YieldRate;

    void Initialize()
    {
        InitMA<5>(closedPrices, MA5);
        InitMA<10>(closedPrices, MA10);
        InitMA<20>(closedPrices, MA20);
        InitMA<60>(closedPrices, MA60);
        InitMA<120>(closedPrices, MA120);
        InitMA<250>(closedPrices, MA250);

        InitMACD<12, 26, 9>(closedPrices);

        InitYieldRate();
    }

    void Reset()
    {
        MA5.resize(MA5.size(), 0.0);
    }

    template<uint32_t count>
    void InitMA(const std::vector<double>& in_prices, std::vector<double>& out_ma)
    {
        if (in_prices.size() >= count)
        {
            std::deque<double> local_price(in_prices.begin(), in_prices.begin() + count - 1);

            std::vector<double> prePrices;
            for (size_t i = 0; i < count; i++)
            {
                prePrices.push_back(in_prices[i]);
                out_ma.push_back(std::reduce(prePrices.begin(), prePrices.end()) / prePrices.size());
            }

            for (size_t i = count - 1; i < in_prices.size(); i++)
            {
                local_price.push_back(in_prices[i]);
                out_ma.push_back(std::reduce(local_price.begin(), local_price.end()) / local_price.size());
                local_price.pop_front();
            }
        }
    }
    template<uint32_t shortPeriod, uint32_t longPeriod, uint32_t signalPeriod>
    void InitMACD(const std::vector<double>& in_prices)
    {
        double shortEMA = 0.0;
        double longEMA = 0.0;
        double DEA = 0.0;

        const double shortAlpha = 2.0 / (shortPeriod + 1.0);
        const double longAlpha = 2.0 / (longPeriod + 1.0);
        const double difAlpha = 2.0 / (signalPeriod + 1.0);

        for (size_t i = 0; i < in_prices.size(); i++)
        {
            auto& price = in_prices[i];
            if (shortEMA == 0.0)
            {
                shortEMA = price;
            }
            else
            {
                shortEMA = shortAlpha * (price - shortEMA) + shortEMA;
            }

            if (longEMA == 0.0)
            {
                longEMA = price;
            }
            else
            {
                longEMA = longAlpha * (price - longEMA) + longEMA;
            }

            double DIF = shortEMA - longEMA;
            if (DEA == 0.0)
            {
                DEA = DIF;
            }
            else
            {
                DEA = difAlpha * (DIF - DEA) + DEA;
            }

            double macd = (DIF - DEA) * 2.0;
            MACD.push_back(macd);
        }
    }

    void InitYieldRate()
    {
        for (const auto& it : closedPrices)
        {
            YieldRate.push_back((it - closedPrices[0]) / closedPrices[0]);
        }
    }
};

struct RecordInfo
{
    uint32_t idx = 0;
    double cash = 0.0;
    double totalAsset = 0.0;
    std::vector<double> meanPrice;
    std::vector<uint32_t> holdCount;
    double principal = 0.0;
    std::vector<double> localYiedRate;
    double globalYieldRate = 0.0;
};

struct StrategyResult
{
    StrategyResult() {}
    StrategyResult(uint32_t in_subjectCount) : subjectCount(in_subjectCount) {}
    uint32_t subjectCount = 0;
    double maxDrawdown = 0.0;
    double maxYieldRate = 0.0;
    std::vector<RecordInfo> recordInfo;
    
    static std::string RecordInfoToCSVString(const RecordInfo& recordInfo, const std::vector<std::shared_ptr<Subject>>& subjects);

    void SaveRecordsToCSV(const std::string& filename, const std::vector<std::shared_ptr<Subject>>& subjects);
};

class StrategyTask;

struct MAInternalInfo
{
    double MA5 = 0.0;
    double MA10 = 0.0;
    double MA20 = 0.0;
    double MA60 = 0.0;
    double MA180 = 0.0;

    std::deque<double> Prices5;
    std::deque<double> Prices10;
    std::deque<double> Prices20;
    std::deque<double> Prices60;
    std::deque<double> Prices180;
};

#define UPDATE_MA(N) \
do                                                                             \
{                                                                              \
    stat.Prices##N[idx].push_back(price);                                      \
    if (stat.Prices##N[idx].size() == ##N + 1) stat.Prices##N[idx].pop_front();\
    double sum = 0.0;                                                          \
    for (const auto it : stat.Prices##N[idx])sum += it;                        \
    stat.MA##N[idx] = sum / stat.Prices##N[idx].size();                        \
} while (false);                                                               \

class BaseStrategy
{
friend StrategyTask;
public:
    struct Param
    {
        uint32_t subjectCount = 0;
        uint32_t subjectLength = 0;

        double startCash = 0.0;
        std::vector<std::shared_ptr<Subject>> subjects;
    };
    BaseStrategy(BaseStrategy::Param in_param)
    {
        param = in_param;
        result.subjectCount = in_param.subjectCount;
    }

    struct StatData
    {
        // Pre stat data
        double cash = 0.0;
        double principle = 0.0;
        double totalAsset = 0.0;
        double globalYieldRate = 0.0;

        std::vector<double> costCash;
        std::vector<double> meanPrice;
        std::vector<double> startPrice;
        std::vector<uint32_t> holdCount;

        uint32_t lastIndex = 0;
        std::vector<double> localYiedRate;
    };
protected:
    Param param;
protected:
    bool bEnableRecord = true;

    StatData stat;

    StrategyResult result;

public:
    
    Param GetParam()
    {
        return param;
    }

    StrategyResult& GetResult()
    {
        return result;
    }
    void SetRecord(bool in_bEnableRecord)
    {
        bEnableRecord = in_bEnableRecord;
        result.recordInfo.clear();

        if (!bEnableRecord)
        {
            result.recordInfo.emplace_back();
        }
    }
    virtual void Reset()
    {
        uint32_t count = param.subjectCount;
        
        stat.cash = param.startCash;
        stat.principle = param.startCash;
        stat.totalAsset = param.startCash;
        stat.globalYieldRate = 0.0;

        stat.costCash.clear();stat.costCash.resize(count, 0.0);
        stat.meanPrice.clear(); stat.meanPrice.resize(count, 0.0);
        stat.startPrice.clear(); stat.startPrice.resize(count, 0.0);
        stat.holdCount.clear(); stat.holdCount.resize(count, 0.0);

        stat.lastIndex = 0;
        stat.localYiedRate.clear(); stat.localYiedRate.resize(count, 0.0);

        result = StrategyResult(param.subjectCount);
    }
    virtual void DumpParameters();

    double GetMeanPrice(uint32_t idx)
    {
        return stat.costCash[idx] / stat.holdCount[idx];
    }
    void BuyInPrice(double money, double p, uint32_t idx);
    void SaleInPrice(double money, double p, uint32_t idx);
    void SaleInCount(uint32_t count, double p, uint32_t idx);

private:
    virtual void Do(uint32_t idx) = 0;
    virtual double UpdatePrinciple();
    virtual double UpdateTotalAsset();
    virtual void PreUpdateStat();
    virtual void PostUpdateStat(uint32_t idx);
    virtual void UpdateResult();
};

#if 0
class GREEDStrategy : public BaseStrategy
{
private:
    double everyDayCash = 0.0;
    double baseExtraCash = 0.0;

    double stopProfitThreadHold = std::numeric_limits<double>::max();
    double saleRate = 0.0;
private:
    double maxDrawdown = std::numeric_limits<double>::min();
    double maxPrice = std::numeric_limits<double>::min();
    double startPrice = 0.0;
public:
    //void Init(double in_startCash, double in_everyDayCash, double in_baseExtraCash, double in_stopProfitThreadHold, double in_saleRate);
    virtual void Reset() override;
    virtual void DumpParameters() override final;
private:
    virtual void Do(uint32_t idx) override final;
    virtual void UpdateResult() override final;
};

class MACDStrategy : public BaseStrategy
{
private:
    double everyDayCash = 0.0;
    double baseExtraCash = 0.0;
private:
    bool lastMACDNegative = true;
    double macd = 0.0;
private:
    double maxDrawdown = std::numeric_limits<double>::min();
    double startPrice = 0.0;
public:
    //void Init(double in_startCash, double in_everyDayCash);
    virtual void Reset() override;
    virtual void DumpParameters() override final;
private:
    virtual void Do(uint32_t idx) override final;
    virtual void UpdateResult() override final;
};

class NINEStrategy : public BaseStrategy
{
private:
    double everyDayCash = 0.0;
private:
    std::queue<double> fourDayPrices;
    uint32_t nineUp = 0;
    uint32_t nineDown = 0;
private:
    double maxDrawdown = std::numeric_limits<double>::min();
    double startPrice = 0.0;
public:
    //void Init(double in_startCash, double in_everyDayCash);
    virtual void Reset() override;
    virtual void DumpParameters() override final;
private:
    virtual void Do(uint32_t idx) override final;
    virtual void UpdateResult() override final;
};

class DCAStrategy : public BaseStrategy
{
private:
    double everyDayCash = 0.0;
    double baseExtraCash = 0.0;

    double growthRate = 1.0;
private:
    double lastDiff = 0.0;
private:
    double maxDrawdown = std::numeric_limits<double>::min();
    double startPrice = 0.0;
public:
    //void Init(double in_startCash, double in_everyDayCash, double in_baseExtraCash, double in_growthRate);
    virtual void Reset() override;
    virtual void DumpParameters() override final;
private:
    virtual void Do(uint32_t idx) override final;
    virtual void UpdateResult() override final;
};
#endif // 0

class GOLDIXICStrategy : public BaseStrategy
{
public:
    struct Param
    {
        double everyDayCash = 0.0;
        double gold_rate = 0.5;
        double ixic_rate = 0.5;
        double refixRate_gold_to_ixic = 1.0;
        double refixRate_ixic_to_gold = 1.0;
    };
    struct StatData
    {
        double gold_asset_rate = 0.5;
        double ixic_asset_rate = 0.5;
    };
private:
    Param param;
    StatData stat;
private:
    double maxDrawdown = std::numeric_limits<double>::min();
    double startPrice = 0.0;
public:
    GOLDIXICStrategy(BaseStrategy::Param baseParam, GOLDIXICStrategy::Param param);
    virtual void Reset() override;
    virtual void DumpParameters() override final;
private:
    virtual void Do(uint32_t idx) override final;
};

class MAGREEDStrategy : public BaseStrategy
{
public:
    struct Param
    {
        double everyDayCash = 0.0;
        double baseExtraCash = 1000.0;
        double minInvestThreadHold = 20000.0;
    };
private:
    Param param;
private:
    double maxDrawdown = std::numeric_limits<double>::min();
    double startPrice = 0.0;
public:
    MAGREEDStrategy(BaseStrategy::Param baseParam, MAGREEDStrategy::Param param);
    virtual void Reset() override;
    virtual void DumpParameters() override final;
private:
    virtual void Do(uint32_t idx) override final;
};

class RemainStrategy : public BaseStrategy
{
public:
    struct Param
    {
        double everyDayCash = 1000.0;
        uint32_t remainDays = 60;
    };
private:
    Param param;
private:
    double maxDrawdown = std::numeric_limits<double>::min();
    double startPrice = 0.0;
public:
    RemainStrategy(BaseStrategy::Param baseParam, RemainStrategy::Param param);
    virtual void Reset() override;
    virtual void DumpParameters() override final;
private:
    virtual void Do(uint32_t idx) override final;
};

class MAStrategy : public BaseStrategy
{
public:
    struct Param
    {
        double everyDayCash = 1000.0;
    };
private:
    Param param;
private:
    double maxDrawdown = std::numeric_limits<double>::min();
    double startPrice = 0.0;
public:
    MAStrategy(BaseStrategy::Param baseParam, MAStrategy::Param param);
    virtual void Reset() override;
    virtual void DumpParameters() override final;
private:
    virtual void Do(uint32_t idx) override final;
};

class WaterChickenStrategy : public BaseStrategy
{
public:
    struct Param
    {
        double everyDayCash = 1000.0;
    };
private:
    Param param;
private:
    double maxDrawdown = std::numeric_limits<double>::min();
    double startPrice = 0.0;

    bool bStartWaterChicken = false;
public:
    WaterChickenStrategy(BaseStrategy::Param in_baseParam, WaterChickenStrategy::Param in_param);
    virtual void Reset() override;
    virtual void DumpParameters() override final;
private:
    virtual void Do(uint32_t idx) override final;
};

class StrategyTask
{
public:
    static std::atomic_uint32_t taskAtomicIdx;
    static std::vector<std::shared_ptr<BaseStrategy>> strategies;
private:
    std::shared_ptr<BaseStrategy> GetNextTask(uint32_t& idx);
    
public:
    StrategyTask();
    void operator()();
};

class StrategyTester
{
private:
    std::vector<std::thread> threads;
    std::vector<StrategyTask> tasks;

    std::vector<std::shared_ptr<BaseStrategy>> strategies;

    uint32_t length = 0;
public:
    StrategyTester(std::vector<std::shared_ptr<BaseStrategy>> in_strategies, uint32_t in_length);

    ~StrategyTester();

    void InitAllTasks();
    
    void Reset();

    void Execute();

    void ReserveNeededStrategies();

    void DumpAllFinalStrategies();
};